package org.qing.config.db;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
public class DbManager {

	// 数据库环境配置对象
	private EnvironmentConfig envConfig;
	// 数据库环境对象
	private Environment myDbEnvironment;
	// 数据库配置对象
	private DatabaseConfig dbConfig;
	// 数据库对象
	private Database myDatabase;
	
	public DbManager() {
		
	}

	public DbManager(BDbConfig bDbConfig) {
		log.debug("bDbConfig:{}", bDbConfig);
		configEnvironment(bDbConfig);
		createDatabase(bDbConfig);
	}

	/**
	 * 向数据库写数据
	 * 
	 * @param <T>
	 * @param key
	 * @param value
	 * @param classZ
	 * @param isOverwrite
	 * @return
	 */
	public <T> int writeToDatabase(String key, T value, Class<T> classZ, boolean isOverwrite) {
		try {
			// JE的记录包含两部分，key键值和value数据值，这两个值都是通过DatabaseEntry对象封装起来的
			// 所以说如果要使用记录，则必须创建两个DatabaseEntry对象，一个是key，一个是value
			// DatabaseEntry内部使用的是bytes数组
			DatabaseEntry databaseKey = new DatabaseEntry(key.trim().getBytes("utf8"));
			DatabaseEntry databaseValue = new DatabaseEntry();
			StoredClassCatalog classCatalog = new StoredClassCatalog(myDatabase);
			EntryBinding<T> myBinding = new SerialBinding<T>(classCatalog,classZ);
			myBinding.objectToEntry(value, databaseValue);
			// 操作状态码
			OperationStatus res = null;
			// 事务配置
			TransactionConfig txConfig = new TransactionConfig();
			// 设置串行化隔离级别
			txConfig.setSerializableIsolation(true);
			// 事务对象，开始事物
			Transaction txn = myDbEnvironment.beginTransaction(null, txConfig);
			// 添加一条记录。如数据库不支持一个key对应多个data或当前数据库中已经存在该key了，则使用此方法将使用新的值覆盖旧的值。
			if (isOverwrite) {
				res = myDatabase.put(txn, databaseKey, databaseValue);
			} else {
				// 不管数据库是否允许支持多重记录(一个key对应多个value),只要存在该key就不允许添加，并且返回perationStatus.KEYEXIST信息
				res = myDatabase.putNoOverwrite(txn, databaseKey, databaseValue);
			}
			txn.commit();// 提交事务
			if (res == OperationStatus.SUCCESS) {
				log.debug("insert success,{}:{}", key, value);
				return 1;
			} else if (res == OperationStatus.KEYEXIST) {
				log.debug("key exist,{}", key);
				return 0;
			} else {
				log.error("insert fail,{}:{}", key, value);
				return 0;
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("insert fail,{}:{},{}", key, value, e.getMessage());
			return 0;
		}
	}

	/**
	 * 从数据库读取相应键值的数据
	 * 
	 * @param <T>
	 * @param key
	 * @return
	 */
	public <T> T readFromDatabase(String key, Class<T> classZ) {
		try {
			DatabaseEntry databaseKey = new DatabaseEntry(key.trim().getBytes("utf8"));
			DatabaseEntry databaseValue = new DatabaseEntry();
			StoredClassCatalog classCatalog = new StoredClassCatalog(myDatabase);
			EntryBinding<T> myBinding = new SerialBinding<T>(classCatalog,classZ);
			TransactionConfig txConfig = new TransactionConfig();// 事务配置
			txConfig.setSerializableIsolation(true);// 设置串行化隔离级别
			// 事务对象,开始事务
			Transaction txn = myDbEnvironment.beginTransaction(null, txConfig);
			OperationStatus res = myDatabase.get(txn, databaseKey, databaseValue, LockMode.DEFAULT);
			txn.commit();// 提交事务
			if (res == OperationStatus.SUCCESS) {
				T foundData = myBinding.entryToObject(databaseValue);
				log.debug("select success,{}:{},{}", key, foundData);
				return foundData;
			} else {
				log.debug("select fail,{},res:{}", key,res.name());
				return null;
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("select fail,{},{}", key, e.getMessage());
			return null;
		}
	}

	/**
	 * 遍历数据库中数据
	 * 
	 * @param <T>
	 * @param classZ
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public <T> List<T> getAllFromDatabase(Class<T> classZ) {
		Cursor myCursor = null;// 游标
		List<T> resultList = new ArrayList<T>();
		Transaction txn = myDbEnvironment.beginTransaction(null, null);
		CursorConfig cc = new CursorConfig();// 游标配置
		cc.setReadCommitted(true);// 设置隔离级别
		if (myCursor == null) {
			myCursor = myDatabase.openCursor(txn, cc);
		}
		DatabaseEntry entryKey = new DatabaseEntry();
		DatabaseEntry entryValue = new DatabaseEntry();
		StoredClassCatalog classCatalog = new StoredClassCatalog(myDatabase);
		EntryBinding<T> myBinding = new SerialBinding<T>(classCatalog,classZ);
		if (myCursor.getFirst(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			T foundData = null;
			try {
				foundData = myBinding.entryToObject(entryValue);
				resultList.add(foundData);
				log.debug("entryValue:{}",entryValue);
			}catch (Exception e) {
				log.debug("entryValueException:{}",entryValue);
			}
			while (myCursor.getNext(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				try {
					foundData = (T)(myBinding.entryToObject(entryValue));
					resultList.add(foundData);
					log.debug("entryValue:{}",entryValue);
				}catch (Exception e) {
					log.debug("entryValueException:{}",entryValue);
				}
			}
		}
		myCursor.close();
		txn.commit();
		return resultList;
	}
	
	/**
	 * 遍历数据库中数据
	 * 
	 * @param <T>
	 * @param classZ
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public <T> Map<String,T> getAllFromDatabaseToMap(Class<T> classZ) {
		Cursor myCursor = null;// 游标
		Map<String,T> resultList = new HashMap<String,T>();
		Transaction txn = myDbEnvironment.beginTransaction(null, null);
		CursorConfig cc = new CursorConfig();// 游标配置
		cc.setReadCommitted(true);// 设置隔离级别
		if (myCursor == null) {
			myCursor = myDatabase.openCursor(txn, cc);
		}
		DatabaseEntry entryKey = new DatabaseEntry();
		DatabaseEntry entryValue = new DatabaseEntry();
		StoredClassCatalog classCatalog = new StoredClassCatalog(myDatabase);
		EntryBinding<T> myBinding = new SerialBinding<T>(classCatalog,classZ);
		if (myCursor.getFirst(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			T foundData = null;
			try {
				foundData = myBinding.entryToObject(entryValue);
				resultList.put(new String(entryKey.getData()), foundData);
			}catch (Exception e) {
				log.debug("entryValueException:{}",entryValue);
			}
			while (myCursor.getNext(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				try {
					foundData = myBinding.entryToObject(entryValue);
					resultList.put(new String(entryKey.getData()), foundData);
				}catch (Exception e) {
					log.debug("entryValueException:{}",entryValue);
				}
			}
		}
		myCursor.close();
		txn.commit();
		return resultList;
	}

	/**
	 * 遍历数据库中数据
	 * 
	 * @param <T>
	 * @param classZ
	 * @param searchs key value
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public <T> List<T> getSearchFromDatabase(Class<T> classZ, Map<String, Object> searchs)
			throws UnsupportedEncodingException {
		Cursor myCursor = null;// 游标
		List<T> resultList = new ArrayList<T>();
		Transaction txn = myDbEnvironment.beginTransaction(null, null);
		CursorConfig cc = new CursorConfig();// 游标配置
		cc.setReadCommitted(true);// 设置隔离级别
		if (myCursor == null) {
			myCursor = myDatabase.openCursor(txn, cc);
		}
		DatabaseEntry entryKey = new DatabaseEntry();
		DatabaseEntry entryValue = new DatabaseEntry();
		StoredClassCatalog classCatalog = new StoredClassCatalog(myDatabase);
		EntryBinding<T> myBinding = new SerialBinding<T>(classCatalog,classZ);
		if (myCursor.getFirst(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			if (searchs == null || searchs.size() == 0) {
				T foundData = null;
				try {
					foundData = myBinding.entryToObject(entryValue);
					resultList.add(foundData);
				}catch (Exception e) {
					log.debug("entryValueException:{}",entryValue);
				}
				while (myCursor.getNext(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					try {
						foundData = (T)(myBinding.entryToObject(entryValue));
						resultList.add(foundData);
					}catch (Exception e) {
						log.debug("entryValueException:{}",entryValue);
					}
				}
			} else {
				
				T foundData = null;
				boolean flg = true;
				try {
					for (String fieldName : searchs.keySet()) {
						Object value = searchs.get(fieldName);
						if (!value.equals(getFieldValueByFieldName(fieldName, foundData))) {
							flg = false;
						}
					}
					if (flg) {
						resultList.add(foundData);
					}
				}catch (Exception e) {
					log.debug("entryValueException:{}",entryValue);
				}
				while (myCursor.getNext(entryKey, entryValue, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					try {
						foundData = myBinding.entryToObject(entryValue);
						flg = true;
						for (String fieldName : searchs.keySet()) {
							Object value = searchs.get(fieldName);
							if (!value.equals(getFieldValueByFieldName(fieldName, foundData))) {
								flg = false;
							}
						}
						if (flg) {
							resultList.add(foundData);
						}
					}catch (Exception e) {
						log.debug("entryValueException:{}",entryValue);
					}
				}
			}
		}
		myCursor.close();
		txn.commit();
		return resultList;
	}

	/**
	 * 根据属性名获取属性值
	 * 
	 * @param fieldName
	 * @param object
	 * @return
	 */
	private Object getFieldValueByFieldName(String fieldName, Object object) {
		try {
			// Field field = object.getClass().getDeclaredField(fieldName);
			Field field = getFieldByClasss(fieldName, object);
			// 设置对象的访问权限，保证对private的属性的访问
			field.setAccessible(true);
			return field.get(object);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 根据属性名获取属性元素，包括各种安全范围和所有父类
	 * 
	 * @param fieldName
	 * @param object
	 * @return
	 */
	private Field getFieldByClasss(String fieldName, Object object) {
		Field field = null;
		Class<?> clazz = object.getClass();

		for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
			try {
				field = clazz.getDeclaredField(fieldName);
			} catch (Exception e) {
				// 这里甚么都不能抛出去。
				// 如果这里的异常打印或者往外抛，则就不会进入
				e.printStackTrace();
			}
			if (field != null) {
				return field;
			}
		}
		return field;
	}

	/**
	 * 删除数据库中一条数据
	 * 
	 * @param key
	 * @return
	 */
	public int deleteFromDatabase(String key) {
		TransactionConfig txConfig = new TransactionConfig();
		txConfig.setSerializableIsolation(true);
		Transaction txn = myDbEnvironment.beginTransaction(null, txConfig);
		DatabaseEntry databaseKey = null;
		try {
			databaseKey = new DatabaseEntry(key.trim().getBytes("utf8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		OperationStatus res = myDatabase.delete(txn, databaseKey);
		txn.commit();
		if (res == OperationStatus.SUCCESS) {
			log.error("delete success,{}", key);
			return 1;
		} else if (res == OperationStatus.KEYEMPTY) {
			log.error("no key,{}", key);
			return 0;
		} else {
			log.error("delete fail,{}", key);
			return 0;
		}
	}

	// 配置创建环境对象
	public void configEnvironment(BDbConfig bDbConfig) {
		log.debug("bDbConfig:{}", bDbConfig);
		envConfig = new EnvironmentConfig();
		// 如果设置了true则表示当数据库环境不存在时候重新创建一个数据库环境，默认为false.
		envConfig.setAllowCreate(bDbConfig.getEnvConfig().getAllowCreate());
		// 事务支持,如果为true，则表示当前环境支持事务处理，默认为false，不支持事务处理。
		envConfig.setTransactional(bDbConfig.getEnvConfig().getTransactional());
		// 是否以只读方式打开，默认为false.
		envConfig.setReadOnly(bDbConfig.getEnvConfig().getReadOnly());
		// 设置当前环境能够使用的RAM占整个JVM百分比
		envConfig.setCachePercent(bDbConfig.getEnvConfig().getCachePercent());
		// 设置当前环境能使用的最大RAM,单位为byte
		envConfig.setCacheSize(bDbConfig.getEnvConfig().getCacheSize());
		File fc = new File(bDbConfig.getEnvConfig().getEnvDir());
		if (!fc.exists() && bDbConfig.getEnvConfig().getAllowCreate()) {
			fc.mkdirs();
		}
		myDbEnvironment = new Environment(fc, envConfig);
		log.debug("myDbEnvironment:{}", myDbEnvironment);
	}

	// 配置创建完环境对象后，可以用它创建数据库
	public void createDatabase(BDbConfig bDbConfig) {
		log.debug("bDbConfig:{}", bDbConfig);
		dbConfig = new DatabaseConfig();
		// 如果设置了true则表示当数据库不存在时候重新创建一个数据库，默认为false.
		dbConfig.setAllowCreate(bDbConfig.getDbConfig().getAllowCreate());
		// 事务支持,如果为true，则表示当前数据库支持事务处理，默认为false，不支持事务处理。
		dbConfig.setTransactional(bDbConfig.getDbConfig().getTransactional());
		// 是否以只读方式打开，默认为false.
		dbConfig.setReadOnly(bDbConfig.getDbConfig().getReadOnly());
//		dbConfig.setBtreeComparator();//设置用于Btree比较的比较器，通常是用来排序
//		dbConfig.setDuplicateComparator();//设置用来比较一个key有两个不同值的时候的大小比较器。
//		dbConfig.setSortedDuplicates(true);//设置一个key是否允许存储多个值，true代表允许，默认false.
//		dbConfig.setExclusiveCreate(true);//以独占的方式打开，也就是说同一个时间只能有一实例打开这个database。
		myDatabase = myDbEnvironment.openDatabase(null, bDbConfig.getDbConfig().getDbName(), dbConfig);
		log.debug("myDatabaseName:{}", myDatabase.getDatabaseName());
	}

}